package com.example.demo.officeManager;

import org.apache.commons.lang3.StringUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jodconverter.core.DocumentConverter;
import org.jodconverter.core.office.OfficeException;
import org.jodconverter.core.office.OfficeManager;
import org.jodconverter.core.office.OfficeUtils;
import org.jodconverter.local.LocalConverter;
import org.jodconverter.local.office.LocalOfficeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * office进程管理器 周期追随系统
 * @author lizg
 * @date 2021/12/10
 */
//@Component
//@Order(Ordered.HIGHEST_PRECEDENCE)
//public class OfficePluginManager{
@Component
@Order(Ordered.LOWEST_PRECEDENCE)
public class OfficePlugin446Manager implements ApplicationRunner{

    private final Logger logger = LoggerFactory.getLogger(OfficePlugin446Manager.class);

    private LocalOfficeManager officeManager;

    @Value("${office.plugin.server.ports:2001,2002}")
    private String serverPorts;

	/** task 超时时间 单位:分钟 */
    @Value("${office.plugin.task.timeout:5}")
    private String timeOut;

    @Value("${office.path:}")
    private String officePath;

    @Value("${office.config.file:config/config.xml}")
    private String officeConfigFile;

    //20220814,version1.2,引用组件所指定的端口号,如无指定标识为0000,则启用约定默认
    @Value("${libre.port:0000}")
    private String librePort;

  //20220814,version1.2,新服务需主动注册,不注册讲无法使用
    @Value("${libre.client:0000}")
    private String libreClient;

  //20220814,version1.2,原有服务主动注册
    public static Map<String,String> AUTO_REGISTER_CLIENT = new HashMap<String,String>();
    static {
//    	AUTO_REGISTER_CLIENT.put("FrameServiceApplication","2001,2002");
//    	AUTO_REGISTER_CLIENT.put("CommonBusinessServiceApplication","2003");
//    	AUTO_REGISTER_CLIENT.put("FimApplication","2004");
//    	AUTO_REGISTER_CLIENT.put("HrApplication","2005");
    	//用全路径方式解决客开问题
    	AUTO_REGISTER_CLIENT.put("com.xisoft.frame.FrameServiceApplication","2001,2002");
//		其它服务要求使用libreClient与librePort配置,此处不再给默认配置,保留逻辑便于维护调整
//    	AUTO_REGISTER_CLIENT.put("com.xisoft.cbs.CommonBusinessServiceApplication","2003");
//    	AUTO_REGISTER_CLIENT.put("com.xisoft.FimApplication","2004");
//    	AUTO_REGISTER_CLIENT.put("com.xisoft.hr.HrApplication","2005");

    }

	public LocalOfficeManager getOfficeManager(){
		return officeManager;
	}

    /**
     * 启动Office组件进程
     */
    @Override
    public void run(ApplicationArguments args) {
    	logger.info(" 开始寻找【libreOffice组件】主目录 ");

    	File officeHome = new File("D:\\XISOFT\\tools\\windows-office");
    	if(officePath != null && !"".equals(officePath)){
    		officeHome = new File(officePath);
    		if(!new File(officeHome, "program/soffice.bin").isFile()){
    			officeHome = null;
    		}
    	}
    	if(officeHome == null && officeConfigFile != null && !"".equals(officeConfigFile)){
    		logger.info("---加载office外部配置文件---:" + officeConfigFile);
    		String fileType = officeConfigFile.substring(officeConfigFile.lastIndexOf(".") + 1);
    		if("xml".equals(fileType)){
				try {
	    			File file = new File(officeConfigFile);
					Document doc = new SAXReader().read(file);
					Element rootElem = doc.getRootElement();
	                Element officeElem = rootElem.element("OfficeConfig");
	                List<Element> elist = officeElem.elements();
	                String path = "";
	                String ports = "";
					String timeOutCfg = "";
	                for (Element element : elist) {
	                	Attribute attName = element.attribute("name");
	                	String name = attName.getValue();
	                	Attribute attValue = element.attribute("value");
	                	String nameValue = attValue.getValue();
	                	 switch (name) {
		                     case "path":
		                    	 path = nameValue;
		                         break;
		                     case "ports":
		                    	 ports = nameValue;
		                         break;
							 case "timeout":
								 timeOutCfg = nameValue;
								 break;
	                	 }
	                }
	                officeHome = new File(path);
	                if(!new File(officeHome, "program/soffice.bin").isFile()){
	                	officeHome = null;
	                }
	                if(!"".equals(ports)){
	                	serverPorts = ports;
	                }
					if(!"".equals(timeOutCfg)){
						timeOut = timeOutCfg;
					}
				} catch (Exception e) {
					logger.error("找不到office组件,请确认'office.home'配置是否有误,将使用默认路径");
				}

    		}else{
    			logger.warn("---加载非xml格式的office外部配置文件待后续支持---");
    		}
    	}
    	if(officeHome == null){
    		officeHome = OfficeUtils.getDefaultWorkingDir();
    	}
    	//2021.12.20
    	if(officeHome == null){
    		String currentDir = System.getProperty("user.dir");
    		String temp = currentDir + File.separator + "windows-office";
    		File home = new File(temp);
    		if(home.isFile()){
    			officeHome = home;
    		}
    	}
        if (officeHome == null) {
        	logger.error("找不到office组件，请确认'office.home'配置是否有误");
        }

		//String clientName  = getClientName();
		String clientName  = "com.xisoft.frame.FrameServiceApplication";
		logger.info("clientName==="+clientName);
		if(StringUtils.isEmpty(clientName)) {
			logger.warn(" 当前应用未配置【libreOffice组件】启动类,无需启动客户端! ");
			return;
		}
        boolean existRun = isExistStart();
		logger.info("existRun:{}",existRun);
		if(!existRun){
			logger.warn(" 当前应用【libreOffice组件未启动】 ");
		}
		else{
			logger.info("-----------------------" + clientName + ":开始加载libreoffice服务");
			if (killProcessByClient(clientName)) {
				logger.warn("检测到有正在运行的office进程，已自动结束该进程，已休眠3秒等待window服务关闭");
			}
		}

        try {
         	//使用默认的office配置
			String portStr = getPortByClient(clientName);
			logger.info(" 获取到启用端口：" + portStr);
			String []portsString = portStr.split(",");
			if(clientName.contains("FrameService")) {
				portsString = portStr.split(",");//平台加载从config.xml中获取
			}
			logger.info(" 【libreOffice组件】启用端口:"  + portStr);
			int[] ports = Arrays.stream(portsString).mapToInt(Integer::parseInt).toArray();
			Long timeMinute = Long.parseLong(timeOut);
			officeManager = LocalOfficeManager.builder()
					.officeHome(officeHome)
					.portNumbers(ports)
					.taskExecutionTimeout(60000L*timeMinute)
					.maxTasksPerProcess(100)
					.build();
             officeManager.start();
			 // 由于启动 office是异步,暂时没有找到同步的方法,
			 // 这里等待3秒再检查
			Thread.sleep(3000);
			boolean isRunning = isExistStart();
			if(!isRunning){
				logger.error(" 当前应用【libreOffice组件未启动】 请查看log日志对应异常堆栈信息 ");
			}
         } catch (Exception e) {
			System.err.println("启动LibreOffice组件失败，请查阅安装指南第5章第2节配置和检查LibreOffice,具体异常："+e.getMessage()+" ！！！ ");
			logger.error("启动LibreOffice组件失败，请查阅安装指南第5章第2节配置和检查LibreOffice！！！具体异常：",e);
         }catch(Error e){
			System.err.println("启动【LibreOffice】组件失败，请查阅安装指南第5章第2节配置和检查LibreOffice,具体异常："+e.getMessage()+" ！！！ ");
			logger.error("启动【LibreOffice】组件失败，请查阅安装指南第5章第2节配置和检查LibreOffice！！！具体异常：",e);
         }
    }

    /**
     * 检测进程是否已启动
     * @return
     */
    public boolean isExistStart() {

    	boolean flag = false;
    	Properties props = System.getProperties();
    	try {
            if (props.getProperty("os.name").toLowerCase().contains("windows")) {
                Process p = Runtime.getRuntime().exec("cmd /c tasklist ");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream os = p.getInputStream();
                byte[] b = new byte[256];
                while (os.read(b) > 0) {
                    baos.write(b);
                }
                String s = baos.toString();
                if (s.contains("soffice.bin")) {
                    flag = true;
                }
            } else {
                Process p = Runtime.getRuntime().exec(new String[]{"sh","-c","ps -ef | grep " + "soffice.bin"});
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                InputStream os = p.getInputStream();
                byte[] b = new byte[256];
                while (os.read(b) > 0) {
                    baos.write(b);
                }
                String s = baos.toString();
                if (StringUtils.ordinalIndexOf(s, "soffice.bin", 3) > 0) {
                    flag = true;
                }
            }
        } catch (IOException e) {
            logger.error("检测office进程异常", e);
        }
    	return flag;
    }

    /**
     * 根据调用服务杀进程
     * @return
     */
    public boolean killProcessByClient(String clientName) {
    	boolean f = false;
    	String port = getPortByClient(clientName);//获取端口
    	String[] portArr = port.split(",");
    	for(String p : portArr) {
    		String pid = getPidByPort(p);
    		f = killProcessByPid(pid);
    		try {
				Thread.sleep(3000);//休眠1.5秒继续
			} catch (InterruptedException e) {
				logger.warn("office进程休眠情况",e);
			}
    	}
    	return f;
    }

    /**
     * 获取调用方服务名称
     */
    public String getClientName1() {
    	String clientName = "";
    	if("0000".equals(libreClient)) {
    		StackTraceElement[] stack = new Throwable().getStackTrace();
        	Set<String> set = AUTO_REGISTER_CLIENT.keySet();
    		for (StackTraceElement s : stack) {
    			String str = s.getClassName();
    			for(String t :set) {
    				if(str.contains(t)) {
        				clientName = t;
        				continue;
        			}
    			}
    	    }
    	}else {
    		clientName = libreClient;
    	}
		return clientName;
    }
    public String getClientName() {
    	String clientName = "";
    	if("0000".equals(libreClient)) {
    		Set<String> set = AUTO_REGISTER_CLIENT.keySet();
    		for(String t :set) {
    			if(containClassName(t)) {
    				clientName = t;
    				continue;
    			}
    		}
    	}else {
    		clientName = libreClient;
    	}
		return clientName;
    }
    /**
     * 得的类全路径
     * @param classNameSource
     * @return
     */
     public boolean containClassName(String classNameSource) {
         StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
         for (StackTraceElement traceElement : stackTrace) {
             String className = traceElement.getClassName();
             if (className.equals(classNameSource)) {
                 return true;
             }
             try {
                 Class<?> aClass = Class.forName(className);
                 Class<?> superclass = aClass.getSuperclass();
                 if (superclass != null) {
                     if (superclass.getName().equals(classNameSource)) {
                         return true;
                     }
                 }
             } catch (ClassNotFoundException e) {
                 e.printStackTrace();
             }
         }
         return false;
     }
    /**
     * 当无配置时,根据调用的服务名获取指定端口号
     */
    private String getPortByClient(String clientName) {
    	String port = "";
		logger.info(" 当前应用配置的 librePort : "+librePort);
    	if("0000".equals(librePort)) {
    		port = AUTO_REGISTER_CLIENT.get(clientName);
    	}else {
    		port = librePort;
    	}
    	return port;
    }
    /**
     * 根据端口号获取pid
     */
    private String getPidByPort(String port) {
    	String pid = null;
    	String s = null;
    	Properties props = System.getProperties();
    	if (props.getProperty("os.name").toLowerCase().contains("windows")) {
    		Process p;
			try {
				p = Runtime.getRuntime().exec("cmd /c netstat -aon|findstr \""+port+"\"");
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
	            InputStream os = p.getInputStream();
	            byte[] b = new byte[256];
	            while (os.read(b) > 0) {
	                baos.write(b);
	            }
	            s = baos.toString();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

            if (s != null && s.contains("LISTENING")) {
            	pid = s.substring(s.lastIndexOf("LISTENING")+9).trim();
            }
    	}
    	return pid;
    }
    /**
     * 根据pid杀进程
     * @return
     */
    private boolean killProcessByPid(String pid) {
    	boolean flag = false;
    	try {
			Runtime.getRuntime().exec("taskkill /f /pid " + pid);
			flag = true;
		} catch (IOException e) {
			e.printStackTrace();
		}
        return flag;
    }

	/**
     * 获取转换器
     * 根据预注册类型及office管理器获取文档转换器
     * @return
     */
    public DocumentConverter getDocumentConverter(OfficeManager officeManager) {
		return LocalConverter.make(officeManager);
    }


    /**
     * 服务停止关闭进程
     */
    @PreDestroy
    public void destroyOfficeManager(){
        if (null != officeManager){
        	try{
        		officeManager.stop();
        	}catch(OfficeException exception){
        		logger.info("Shutting down office exception or no need");
        	}
			finally {
				// Stop the office process
				OfficeUtils.stopQuietly(officeManager);
				logger.info(" >>> office转换服务 finally stopQuietly 停止完成。");
			}
        }
    }

    /**
     * 服务停止关闭进程

    @PreDestroy
    public void destroyOfficeManager(){
        if (null != officeManager && officeManager.isRunning()) {
            logger.info("Shutting down office process");
            officeManager.stop();
        }
    }
     */
}
